package com.sway.data.core;

import com.sway.data.exception.BuildingSqlException;
import com.sway.data.pool.ConnectionPool;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

public class DataTemplate implements BaseOperation {

    private final ConnectionPool connectionPool;

    public DataTemplate(ConnectionPool connectionPool){
        this.connectionPool = connectionPool;
    }

    @Override
    public <T> List<T> select(T search) {
        try(Connection connection = connectionPool.getConnection()) {
            Statement statement = connection.createStatement();
            String sql = SqlBuilder.getBuilder().build(SqlType.SELECT, search);
            System.out.println(sql);
            ResultSet resultSet = statement.executeQuery(sql);
            BaseMapper<T> mapper = new ResultSetMapper<>(search);
            return mapper.result(resultSet);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> select(String sql, Class<T> view, Object... args) {
        try(Connection connection = connectionPool.getConnection()){
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            setParam(preparedStatement,args);
            ResultSet resultSet = preparedStatement.executeQuery();
            BaseMapper<T> mapper = new ResultSetMapper<>(view);
            return mapper.result(resultSet);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> Integer update(String sql, Class<T> view, Object... args) {
        try (Connection connection = connectionPool.getConnection()){
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            setParam(preparedStatement,args);
            return preparedStatement.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> Integer insert(String sql, Class<T> view, Object... args) {
        return update(sql,view,args);
    }

    @Override
    public <T> void update(T update) {
        try (Connection connection = connectionPool.getConnection()){
            String sql = SqlBuilder.getBuilder().build(SqlType.UPDATE, update);
            Statement statement = connection.createStatement();
            System.out.println(sql);
            int execute = statement.executeUpdate(sql);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    @Override
    public <T> void delete(T delete) {
        throw new BuildingSqlException("Not Support Delete Method");
    }

    @Override
    public <T> void insert(T entity) {
        try(Connection connection = connectionPool.getConnection()){
            String sql = SqlBuilder.getBuilder().build(SqlType.INSERT, entity);
            Statement statement = connection.createStatement();
            System.out.println(sql);
            statement.executeUpdate(sql);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T> void batchInsert(List<T> entities) {
        try (Connection connection = connectionPool.getConnection()){
            String sql = SqlBuilder.getBuilder().batchBuild(entities);
            Object[] params = SqlBuilder.getBuilder().paramBuild(entities);
            PreparedStatement ps = connection.prepareStatement(sql);
            setParam(ps,params);
            System.out.println(sql);
            ps.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void setParam(PreparedStatement ps, Object[] params) throws Exception{
        int index = 1;
        if(params.length>0){
            for (Object param : params) {
                setParam(ps,index,param);
                index++;
            }

        }
    }

    private void setParam(PreparedStatement statement, int index, Object param) throws Exception{
        if (param instanceof String) {
            statement.setString(index, (String) param);
        } else if (param instanceof Integer) {
            statement.setInt(index, (Integer) param);
        } else if (param instanceof Enum) {
            statement.setString(index, ((Enum<?>) param).name());
        } else if (param instanceof LocalDateTime) {
            statement.setTimestamp(index, Timestamp.valueOf((LocalDateTime) param));
        } else if (param instanceof Boolean) {
            statement.setBoolean(index, (Boolean) param);
        } else if (param instanceof Long) {
            statement.setLong(index, (Long) param);
        } else if (param instanceof Double) {
            statement.setDouble(index, (Double) param);
        } else if (param instanceof BigDecimal) {
            statement.setBigDecimal(index, (BigDecimal) param);
        } else if (param instanceof LocalDate) {
            statement.setDate(index, Date.valueOf((LocalDate) param));
        } else if (param == null) {
            statement.setObject(index, null);
        } else {
            throw new BuildingSqlException("unsupported param type");
        }
    }

}
